package org.salatec.tapestry4e.util;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import org.apache.tapestry5.annotations.Parameter;
import org.apache.tapestry5.annotations.Property;
import org.apache.tapestry5.ioc.internal.services.ClassNameLocatorImpl;
import org.apache.tapestry5.ioc.internal.services.ClasspathURLConverterImpl;
import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
import org.apache.tapestry5.ioc.services.ClassNameLocator;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

/**
 * This class is used to locate the component classes and their properties and
 * parameters.
 * 
 * @author Juan M. Salamanca
 * 
 */
public class TapestryClassFinder {

	private static final String TAPESTRY_COMPONENTS_ROOT = "org.apache.tapestry5.corelib";
	private final static Set<String> SUPPORTED_SUBPACKAGES = CollectionFactory
			.newSet("base", "components");
	private static final String FILE_NAME = "tapestryInfo.xml";

	public TapestryClassFinder() {

	}

	/**
	 * Returns the list of tapestry 5 components from the core library.
	 * 
	 * @return
	 * @throws IOException
	 */
	private List<String> findCoreTapestryComponentsFullName() {

		List<String> classes = new ArrayList<String>();

		Iterator<String> it = SUPPORTED_SUBPACKAGES.iterator();
		Thread thread = Thread.currentThread();
		ClassLoader loader = thread.getContextClassLoader();
		thread.setContextClassLoader(this.getClass().getClassLoader());
		testCL();
		try {
			ClassNameLocator CLASS_LOCATOR = new ClassNameLocatorImpl(
					new ClasspathURLConverterImpl());
			while (it.hasNext()) {
				List<String> tmp = (List<String>) CLASS_LOCATOR
						.locateClassNames(TAPESTRY_COMPONENTS_ROOT + "."
								+ it.next());

				classes.addAll(tmp);
			}
		} finally {
			thread.setContextClassLoader(loader);
		}

		return classes;
	}

	private void testCL() {
		ClassLoader loader = Thread.currentThread().getContextClassLoader();
		try {
			Enumeration e = loader.getResources(TAPESTRY_COMPONENTS_ROOT);
		} catch (IOException e) {
			Logger.logException(e);
			e.printStackTrace();
		}
	}

	public List<String> findCoreComponentNames() {

		List<String> classes;
		List<String> names = new ArrayList<String>();

		classes = findCoreTapestryComponentsFullName();

		Iterator<String> it = classes.iterator();

		while (it.hasNext()) {
			String fullName = it.next();
			names.add(fullName.substring(fullName.lastIndexOf(".") + 1));
		}

		return names;
	}

	public static List<String> findPropertiesInClass(String className)
			throws ClassNotFoundException {
		return findAnnotationInFields(className, Property.class);
	}

	public static List<String> findParametersInClass(String className)
			throws ClassNotFoundException {
		return findAnnotationInFields(className, Parameter.class);
	}

	private static List<String> findAnnotationInFields(String className,
			Class annotationClass) throws ClassNotFoundException {
		Class cls = Class.forName(className);
		Field[] fields = cls.getDeclaredFields();
		List<String> properties = new ArrayList<String>();
		for (Field field : fields) {
			try {
				if (field.getAnnotation(annotationClass) != null)
					properties.add(field.getName());
			} catch (NullPointerException e) {

			}
		}
		return properties;
	}

	public static void main(String[] args) throws ClassNotFoundException,
			IOException {
		TapestryClassFinder finder = new TapestryClassFinder();
		finder.writeT5DataToFile();
		try {
			List<T5Component> components = finder.parseResources();
			Iterator<T5Component> it = components.iterator();
			while (it.hasNext()) {
				System.out.println(it.next().toString());
			}
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

	}

	public List<T5Component> parseResources() throws Exception {
		List<T5Component> result = new ArrayList<T5Component>();

		DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
		DocumentBuilder builder = factory.newDocumentBuilder();
		Document doc = builder.parse(getClass().getResourceAsStream(FILE_NAME));
		Element root = doc.getDocumentElement();
		NodeList components = root.getChildNodes();
		for (int i = 0; i < components.getLength(); i++) {
			Node node = components.item(i);
			if (node instanceof Element) {
				Element component = (Element) components.item(i);
				T5Component t5c = new T5Component();
				t5c.setName(component.getAttribute("name"));
				NodeList parameters = component
						.getElementsByTagName("parameter");
				List<String> t5pars = new ArrayList<String>();
				for (int j = 0; j < parameters.getLength(); j++) {
					t5pars.add(((Element) parameters.item(j)).getFirstChild()
							.getNodeValue());
				}
				Collections.sort(t5pars);
				t5c.setParameters(t5pars);
				NodeList properties = component
						.getElementsByTagName("property");
				List<String> t5props = new ArrayList<String>();
				for (int k = 0; k < properties.getLength(); k++) {
					t5props.add(((Element) properties.item(k)).getFirstChild()
							.getNodeValue());
				}
				t5c.setProperties(t5props);

				result.add(t5c);
			}
		}
		Collections.sort(result);
		return result;
	}

	private void writeT5DataToFile() {
		List<String> components = findCoreComponentNames();
		List<String> fullComponents = findCoreTapestryComponentsFullName();
		File file = new File(FILE_NAME);

		try {
			if (!file.exists())
				file.createNewFile();
			FileWriter writer = new FileWriter(file);

			writer.write("<Components>");
			for (int i = 0; i < components.size(); i++) {
				String name = components.get(i);
				String className = fullComponents.get(i);
				Class cls = Class.forName(className);
				if (!Modifier.isAbstract(cls.getModifiers())
						&& !cls.isInterface()) {
					writer.write("<Component name=\"" + name + "\" >");
					List<String> parameters = findParametersInClass(className);
					if (parameters != null && parameters.size() > 0) {
						writer.append("<parameters>");
						Iterator<String> it = parameters.iterator();
						while (it.hasNext()) {
							writer.append("<parameter>" + it.next()
									+ "</parameter>");
						}
						writer.append("</parameters>");
					}
					List<String> properties = findPropertiesInClass(className);
					if (properties != null && properties.size() > 0) {
						writer.append("<properties>");
						Iterator<String> it = properties.iterator();
						while (it.hasNext()) {
							writer.append("<property>" + it.next()
									+ "</property>");
						}
						writer.append("</properties>");
					}
					writer.append("</Component>");
				}
			}
			writer.write("</Components>");
			writer.close();

		} catch (Exception e) {
			e.printStackTrace();
			Logger.log(Logger.ERROR, "Error creating File", e);
		}
	}

}
